/*
OpenMVL Matrix Vector Library
Copyright (c) 2009 FluidInteractive (R)
 
This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising
from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it freely,
subject to the following restrictions:
 
1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product documentation
would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must
not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
 
Written by: Nicola Candussi <nicola@fluidinteractive.com>
*/

//assign.hpp

#ifndef MVL_ASSIGN_HPP
#define MVL_ASSIGN_HPP

#include "expr.hpp"

namespace mvl {

template<typename L, typename StartIt1, typename StartIt2>
struct find_first_value {

    typedef L layout_t;
    typedef typename layout::end1<layout_t>::type it1_end;
    typedef StartIt1 start_it1;
    typedef StartIt2 start_it2;

    template<typename It1, typename It2, typename Condition>
    struct state {
        typedef It1         it1;
        typedef It2         it2;
        typedef Condition   condition;
        typedef state       type;
    };

    template<typename It1, typename It2>
    struct check_found {
        typedef typename meta::equal_to<It1, it1_end>::type is_end1;
        typedef typename meta::eval_if<
            is_end1,
            meta::identity<meta::none>,
            layout::end2<It1>
        >::type it2_end;
        typedef typename meta::eval_if<
            is_end1,
            meta::bool_<true>,
            meta::equal_to<It2, it2_end>
        >::type is_end2;
        typedef typename meta::eval_if<
            is_end2,
            layout::entry<meta::int_<0>, meta::int_<0>, layout::_0_>,
            meta::deref<It2>
        >::type entry;
        typedef typename meta::eval_if<
            is_end2,
            meta::bool_<false>,
            meta::is_same<typename entry::tag, layout::_v_>
        >::type type;
    };

    struct update {
        template<typename State>
        struct apply {
            typedef typename layout::end2<typename State::it1>::type it2_end;
            typedef typename meta::equal_to<typename State::it2, it2_end>::type is_end2;

            typedef typename meta::eval_if<
                is_end2,
                meta::next<typename State::it1>,
                meta::identity<typename State::it1>
            >::type new_it1;

            typedef typename meta::eval_if<
                is_end2,
                meta::eval_if<
                    typename meta::equal_to<new_it1, it1_end>::type,
                    meta::identity<meta::none>,
                    layout::begin2<new_it1>
                >,
                meta::next<typename State::it2>
            >::type new_it2;

            typedef typename check_found<new_it1, new_it2>::type found;
            typedef typename meta::and_<
                typename meta::not_<found>::type,
                typename meta::not_equal_to<new_it1, it1_end>::type
            >::type new_condition;
            typedef state<new_it1, new_it2, new_condition> type;
        };

    };

    typedef typename check_found<start_it1, start_it2>::type found; 
    typedef typename meta::while_<
        state<
            start_it1,
            start_it2,
            typename meta::and_<
                typename meta::not_<found>::type,
                typename meta::not_equal_to<start_it1, it1_end>::type
            >::type
        >,
        update
    >::type type;
};

template<typename E, typename It1, typename It2>
class assign_proxy 
{
public:

    typedef E expr_t;
    typedef typename E::value_type value_type;
    typedef typename E::layout_t layout_t;
    typedef typename expr_t::closure_t expr_closure_t;
    typedef assign_proxy type;

private:
    typedef It1 it1;
    typedef It2 it2;
    typedef typename layout::end1<layout_t>::type it1_end;
    typedef typename meta::deref<it2>::type entry;

    typedef typename find_first_value<
        layout_t,
        It1,
        typename meta::next<It2>::type
    >::type next;

    struct empty_proxy {
        typedef empty_proxy type;
        empty_proxy(expr_t &e) { }
        void operator , (value_type rhs) {
            MVL_STATIC_CHECK((meta::not_equal_to<typename next::it1, it1_end>::value), "too_many_values");
        }
    };

    typedef typename meta::eval_if<
        typename meta::not_equal_to<typename next::it1, it1_end>::type,
        assign_proxy<expr_t, typename next::it1, typename next::it2>,
        empty_proxy
    >::type next_proxy;

public:
    assign_proxy(expr_t &e) : m_e(e) { }

    next_proxy operator = (value_type rhs) {
        m_e.template at<entry>() = rhs;
        return next_proxy(m_e);
    }

    next_proxy operator , (value_type rhs) {
        m_e.template at<entry>() = rhs;
        return next_proxy(m_e);
    }

private:
    expr_closure_t m_e;
};

template<typename E>
struct assign_proxy_traits {
    typedef typename layout::begin1<typename E::layout_t>::type it1_begin;
    typedef typename layout::begin2<it1_begin>::type it2_begin;
    typedef typename find_first_value<
        typename E::layout_t,
        it1_begin,
        it2_begin
    >::type first;
    typedef assign_proxy<E, typename first::it1, typename first::it2> result_t;
};

template<typename E>
inline
typename assign_proxy_traits<E>::result_t
assign(expr<E> &e) 
{
    typedef typename assign_proxy_traits<E>::result_t proxy;
    return proxy(e());
}

} // namespace mvl


#endif
